#include "CorrelationBasedCF.h"

CorrelationBasedCF::CorrelationBasedCF() {
	active_uid = NOT_FOUND;
	daoFactory = DAOFactory::getDAOFactory();
}

void CorrelationBasedCF::populateRMatrix() {
	list<Preference> preferences;
	try {
		daoFactory->connect_db();
		preferences = daoFactory->getPreferenceDAO()->loadAll();
		active_uid = daoFactory->getUserDAO()->loadUserByDeviceCode(
				active_user_mac).getId();
	} catch (std::exception &ex) {
		Log::error(ex.what());
		return;
	}
	if (preferences.size() == 0)
		return;
	list<Preference>::const_iterator iter = preferences.begin();
	while (iter != preferences.end()) {
		users.insert(iter->getUser().getId());
		itens.insert(iter->getContent().getId());
		ratings.insert(make_pair(make_pair(iter->getUser().getId(),
				iter->getContent().getId()), iter->getRating()));
		iter++;
	}
	for (set<int>::const_iterator u = users.begin(); u != users.end(); u++)
		for (set<int>::const_iterator i = itens.begin(); i != itens.end(); i++)
			if (ratings[make_pair(*u, *i)] == NOT_RATED)
				continue;
}

multimap<string, string> CorrelationBasedCF::startUpList(int n) {
	multimap<string, string> rec_itens;
	if (ratings.empty())
		try {
			vector<int> dataset;
			list<Content> contents = daoFactory->getContentDAO()->loadAll();
			for (list<Content>::const_iterator it = contents.begin(); it
					!= contents.end(); it++)
				dataset.push_back(it->getId());
			newUserMethod = new RandomMethod(dataset);
			dataset = newUserMethod->getItens();
			for (unsigned i = 0; i != dataset.size() and rec_itens.size()
					!= (unsigned) n; i++) {
				Content c = daoFactory->getContentDAO()->loadContentById(
						dataset[i]);
				rec_itens.insert(make_pair(c.getGenre().getName(), c.getName()));
			}
		} catch (std::exception &ex) {
			Log::error(ex.what());
		}
	else if (active_uid == NOT_FOUND) {
		vector<int> dataset;
		switch (NewUserMethod::method) {
		case RANDOM: {
			Log::verbose("Random selection of itens for new user");
			list<Content> contents = daoFactory->getContentDAO()->loadAll();
			for (list<Content>::const_iterator it = contents.begin(); it
					!= contents.end(); it++)
				dataset.push_back(it->getId());
			newUserMethod = new RandomMethod(dataset);
			break;
		}
		case POPULARITY:
			Log::verbose("Popular selection  of itens for new user");
			newUserMethod = new PopularityMethod(ratings);
			break;
		default:
			break;
		}

		try {
			dataset = newUserMethod->getItens();
			for (unsigned i = 0; i != dataset.size() and rec_itens.size()
					!= (unsigned) n; i++) {
				Content c = daoFactory->getContentDAO()->loadContentById(
						dataset[i]);
				rec_itens.insert(make_pair(c.getGenre().getName(), c.getName()));
			}
		} catch (std::exception &ex) {
			Log::error(ex.what());
		}
	} else
		try {
			list<Content> contents = daoFactory->getContentDAO()->loadAll();
			for (list<Content>::const_iterator it = contents.begin(); it
					!= contents.end() and rec_itens.size() != (unsigned) n; it++)
				if (itens.find(it->getId()) == itens.end())
					rec_itens.insert(make_pair(it->getGenre().getName(),
							it->getName()));
				else {
					matrix::const_iterator rating = ratings.find(make_pair(
							active_uid, it->getId()));
					if (rating != ratings.end() and rating->second == NOT_RATED)
						rec_itens.insert(make_pair(it->getGenre().getName(),
								it->getName()));
				}
		} catch (std::exception &ex) {
			Log::error(ex.what());
		}

	return rec_itens;
}

multimap<string, string> CorrelationBasedCF::getTopNList(int n) {
	populateRMatrix();
	if (ratings.empty() or active_uid == NOT_FOUND)
		return startUpList(n);
	multimap<double, int> neighborhood;
	for (set<int>::const_iterator i = users.begin(); i != users.end(); i++)
		if (*i != active_uid)
			neighborhood.insert(make_pair(pearsonCorrelation(*i), *i));
	multimap<int, int> topnlist;
	multimap<double, int>::reverse_iterator riter;
	for (set<int>::const_iterator i = itens.begin(); i != itens.end()
			and topnlist.size() != (unsigned) n; i++)
		if (ratings[make_pair(active_uid, *i)] == NOT_RATED) {
			int sum = 0;
			for (riter = neighborhood.rbegin(); riter != neighborhood.rend(); riter++)
				if (ratings[make_pair(riter->second, *i)] != NOT_RATED)
					sum += ratings[make_pair(riter->second, *i)];
			topnlist.insert(make_pair(sum, *i));
		}
	multimap<string, string> rec_itens;
	multimap<int, int>::reverse_iterator iter;
	for (iter = topnlist.rbegin(); iter != topnlist.rend(); iter++) {
		Content c = daoFactory->getContentDAO()->loadContentById(iter->second);
		rec_itens.insert(make_pair(c.getGenre().getName(), c.getName()));
	}
	if (rec_itens.empty())
		return startUpList(n);
	return rec_itens;
}

double CorrelationBasedCF::predict(int cid) {
	if (ratings.empty())
		return 0;
	avr_a_user = ratingsAverage(active_uid, cid);
	if (active_uid == NOT_FOUND) {
		Log::verbose("Guest user - no ratings to calculate (yet)");
		return 0;
	}
	return calculatePrediction(cid);
}

double CorrelationBasedCF::ratingsAverage(const int& uid, const int& cid) {
	double average = 0;
	int quotient = 0;
	for (matrix::const_iterator iter = ratings.begin(); iter != ratings.end(); iter++)
		if (uid == iter->first.first and cid != iter->first.second
				and iter->second != NOT_RATED) {
			average += iter->second;
			quotient++;
		}
	return average / quotient;
}

double CorrelationBasedCF::pearsonRatingsAverage(const int& uid) {
	int quotient = 0;
	int sum1 = 0, sum2 = 0;
	int term1, term2;

	p_avr_a_user = 0;
	for (set<int>::const_iterator i = itens.begin(); i != itens.end(); i++) {
		term1 = ratings[make_pair(active_uid, *i)];
		if (term1 == 0)
			continue;
		sum1 += term1;

		term2 = ratings[make_pair(uid, *i)];
		if (term2 == 0) {
			sum1 -= term1;
			continue;
		}
		sum2 += term2;

		quotient++;
	}
	if (quotient == 0) {
		p_avr_a_user = 0;
		return 0;
	}
	p_avr_a_user = (double) sum1 / quotient;
	return (double) sum2 / quotient;
}

double CorrelationBasedCF::pearsonCorrelation(const int& uid) {
	double avr_uid = pearsonRatingsAverage(uid);
	int rating_a_uid, rating_uid;
	double factor1, factor2;
	double num_sum = 0, den_sum1 = 0, den_sum2 = 0;

	for (set<int>::const_iterator i = itens.begin(); i != itens.end(); i++) {
		rating_a_uid = ratings[make_pair(active_uid, *i)];
		if (rating_a_uid == 0)
			continue;
		rating_uid = ratings[make_pair(uid, *i)];
		if (rating_uid == 0)
			continue;
		factor1 = rating_a_uid - p_avr_a_user;
		factor2 = rating_uid - avr_uid;
		num_sum += factor1 * factor2;
		den_sum1 += pow(factor1, 2);
		den_sum2 += pow(factor2, 2);
	}
	if (num_sum == 0)
		return 0;
	return num_sum / sqrt(den_sum1 * den_sum2);
}

double CorrelationBasedCF::calculatePrediction(const int& cid) {
	map<int, double> avr_users, pearson_users;
	double num_sum = 0, den_sum = 0;
	int uid;
	for (matrix::const_iterator iter = ratings.begin(); iter != ratings.end(); iter++) {
		if (iter->second == NOT_RATED)
			continue;
		uid = iter->first.first;
		if (uid != active_uid) {
			if (avr_users.find(uid) == avr_users.end()) {
				avr_users[uid] = ratingsAverage(uid, cid);
				pearson_users[uid] = pearsonCorrelation(uid);
				den_sum += abs(pearson_users[uid]);
			}
			if (cid == iter->first.second)
				num_sum += (iter->second - avr_users[uid]) * pearson_users[uid];
		}
	}
	return avr_a_user + num_sum / den_sum;
}

CorrelationBasedCF::~CorrelationBasedCF() {
	if (newUserMethod != NULL)
		delete newUserMethod;
	daoFactory->disconnect_db();
}
